﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using VistaKeysExtender.Properties;
using VistaKeysExtender.WinApi;

namespace VistaKeysExtender
{
	class HotKeysWorker
	{
		private readonly IntPtr _currentHandle;
		private readonly Keys[] _keys = new[] { Keys.Left, Keys.Right, Keys.Up, Keys.Down };
		private readonly IList<HotKey> _hotKeys = new List<HotKey>();

		public HotKeysWorker()
		{
			_currentHandle = GetCurrent();
		}

		public void RegisterHotKeys()
		{
			if (Settings.Default.WindowsAttachedEnable)
				RegisterAttached();
			if (Settings.Default.WindowsMoveEnable)
				RegisterMoved();
			if (Settings.Default.WindowsScreenEnable)
				RegisterScreen();
		}

		private void RegisterAttached()
		{
			HotKey.KeyModifiers modifiers = HotKey.KeyModifiers.Windows |
											(Settings.Default.WindowsAttachedAlt
												 ? HotKey.KeyModifiers.Alt
												 : HotKey.KeyModifiers.None) |
											(Settings.Default.WindowsAttachedCtrl
												 ? HotKey.KeyModifiers.Control
												 : HotKey.KeyModifiers.None) |
											(Settings.Default.WindowsAttachedShift
												 ? HotKey.KeyModifiers.Shift
												 : HotKey.KeyModifiers.None);
			Register(modifiers);
		}

		private void RegisterMoved()
		{
			HotKey.KeyModifiers modifiers = HotKey.KeyModifiers.Windows |
											(Settings.Default.WindowsMoveAlt
												 ? HotKey.KeyModifiers.Alt
												 : HotKey.KeyModifiers.None) |
											(Settings.Default.WindowsMoveCtrl
												 ? HotKey.KeyModifiers.Control
												 : HotKey.KeyModifiers.None) |
											(Settings.Default.WindowsMoveShift
												 ? HotKey.KeyModifiers.Shift
												 : HotKey.KeyModifiers.None);
			Register(modifiers);
		}

		private void RegisterScreen()
		{
			HotKey.KeyModifiers modifiers = HotKey.KeyModifiers.Windows |
											(Settings.Default.WindowsScreenAlt
												 ? HotKey.KeyModifiers.Alt
												 : HotKey.KeyModifiers.None) |
											(Settings.Default.WindowsScreenCtrl
												 ? HotKey.KeyModifiers.Control
												 : HotKey.KeyModifiers.None) |
											(Settings.Default.WindowsScreenShift
												 ? HotKey.KeyModifiers.Shift
												 : HotKey.KeyModifiers.None);
			Register(modifiers);
		}

		private void Register(HotKey.KeyModifiers modifiers)
		{
			foreach (Keys key in _keys)
			{
				try
				{
					HotKey hotKey = new HotKey
					{
						Key = key,
						KeyModifier = modifiers,
						Handle = _currentHandle
					};
					hotKey.HotKeyPressed += HotKeyPressed;
					_hotKeys.Add(hotKey);
				}
				catch
				{
					MessageBox.Show(
									string.Format(
										"Cannot register this hot key '{0} + {1}', maybe already registered by another process.",
										modifiers, key), @"Keys Extenders");
				}
			}
		}

		private static IntPtr GetCurrent()
		{
			using (Process curProcess = Process.GetCurrentProcess())
			using (ProcessModule curModule = curProcess.MainModule)
			{
				if (curModule != null) return curModule.BaseAddress;
			}
			return IntPtr.Zero;
		}

		static void HotKeyPressed(object sender, KeyEventArgs e)
		{
			if (Settings.Default.WindowsAttachedAlt == e.Alt
					&& Settings.Default.WindowsAttachedCtrl == e.Control
					&& Settings.Default.WindowsAttachedShift == e.Shift)
				SetWindowLocation(e.KeyCode);
			else if (Settings.Default.WindowsMoveAlt == e.Alt
					&& Settings.Default.WindowsMoveCtrl == e.Control
					&& Settings.Default.WindowsMoveShift == e.Shift)
				MoveWindow(e.KeyCode);
			else if (Settings.Default.WindowsScreenAlt == e.Alt
					&& Settings.Default.WindowsScreenCtrl == e.Control
					&& Settings.Default.WindowsScreenShift == e.Shift)
				ScreenWindow(e.KeyCode);
		}

		#region Windows moved

		enum WinState
		{
			Normal,
			Minimized,
			Left,
			Right,
			Maximized
		}

		/// <summary>
		/// Get Current Window State
		/// </summary>
		/// <param name="screen"></param>
		/// <param name="rect"></param>
		/// <returns></returns>
		private static WinState GetWindowState(Screen screen, WAWindows.Rect rect)
		{
			if (rect.Width >= screen.WorkingArea.Width && rect.Height >= screen.WorkingArea.Height) // Maximized
				return WinState.Maximized;
			if (rect.Top < -screen.WorkingArea.Height && rect.Left < -screen.WorkingArea.Width)
				return WinState.Minimized;

			if (rect.Height >= screen.WorkingArea.Height && Math.Abs(rect.Width - (screen.WorkingArea.Width / 2)) <= 1)
			{
				if (rect.Left == screen.WorkingArea.Left)
					return WinState.Left;
				return WinState.Right;
			}

			return WinState.Normal;
		}

		private static void SetWindowLocation(Keys k)
		{
			//Active Window
			IntPtr window = WAWindows.GetForegroundWindow();
			// Check SIZEBOX Style (Window can sizable)
			bool isResizableWindow = IsResizableWindow(window);

			WAWindows.Rect rect;
			if (WAWindows.GetWindowRect(window, out rect))
			{
				Screen screen = Screen.FromHandle(window);

				WinState state = GetWindowState(screen, rect);

				if (isResizableWindow && ((k == Keys.Left && state == WinState.Right) || (k == Keys.Right && state == WinState.Left)
					|| (k == Keys.Down && (state == WinState.Maximized || state == WinState.Left || state == WinState.Right))))
				{
					WAWindows.ShowWindow(window, (int)WAWindows.WindowShowStyle.ShowNormal);

					// Fixed problem with restore on other screen
					Screen newScreen = Screen.FromHandle(window);
					if (!newScreen.Equals(screen))
					{
						if (WAWindows.GetWindowRect(window, out rect))
							MoveToNewScreen(window, newScreen, screen, rect, isResizableWindow, false);
					}
				}
				else if (k == Keys.Down)
				{
					WAWindows.ShowWindow(window, (int)WAWindows.WindowShowStyle.ShowMinimized);
				}
				else if (isResizableWindow && k == Keys.Up)
				{
					if (state == WinState.Minimized)
						WAWindows.ShowWindow(window, (int)WAWindows.WindowShowStyle.Restore);
					else
					{
						WAWindows.ShowWindow(window, (int)WAWindows.WindowShowStyle.ShowMaximized);

						if (WAWindows.GetWindowRect(window, out rect))
						{
							WAWindows.SetWindowPos(window, WAWindows.HwndTop,
												   screen.WorkingArea.Left,
												   screen.WorkingArea.Top,
												   screen.WorkingArea.Width, screen.WorkingArea.Height, WAWindows.SwpShowWindow);
						}
					}
				}
				else if (isResizableWindow)
				{
					if (k == Keys.Left)
					{
						rect.Left = screen.WorkingArea.Left;
						rect.Right = screen.WorkingArea.Right - screen.WorkingArea.Width / 2;
					}
					else if (k == Keys.Right)
					{
						rect.Left = screen.WorkingArea.Right - screen.WorkingArea.Width / 2;
						rect.Right = screen.WorkingArea.Right;
					}

					WAWindows.ShowWindow(window, (int)WAWindows.WindowShowStyle.ShowMaximized);
					WAWindows.SetWindowPos(window, WAWindows.HwndTop, rect.Left, screen.WorkingArea.Top,
										   rect.Width, screen.WorkingArea.Bottom, WAWindows.SwpShowWindow);
				}
			}

		}

		private static bool IsResizableWindow(IntPtr window)
		{
			// Fix for Google Chrome
			StringBuilder sb = new StringBuilder(100);
			if (WAWindows.GetClassName(window, sb, sb.Capacity) != IntPtr.Zero
				&& sb.ToString().StartsWith("Chrome_"))
				return true;

			return ((Int64)WAWindows.GetWindowLongPtr(window, WAWindows.GwlStyle) & WAWindows.WsSizebox)
				   == WAWindows.WsSizebox;
		}

		private static void MoveWindow(Keys keys)
		{
			int moveStep = Settings.Default.WindowsMoveStep;
			//Active Window
			IntPtr window = WAWindows.GetForegroundWindow();
			WAWindows.Rect rect;
			if (WAWindows.GetWindowRect(window, out rect))
			{
				Screen screen = Screen.FromHandle(window);
				WinState state = GetWindowState(screen, rect);

				if (state == WinState.Normal)
				{
					if (keys == Keys.Left)
					{
						rect.Right -= moveStep;
						rect.Left -= moveStep;
					}
					if (keys == Keys.Right)
					{
						rect.Right += moveStep;
						rect.Left += moveStep;
					}
					if (keys == Keys.Up)
					{
						rect.Bottom -= moveStep;
						rect.Top -= moveStep;
					}
					if (keys == Keys.Down)
					{
						rect.Bottom += moveStep;
						rect.Top += moveStep;
					}
					if (rect.Top < screen.WorkingArea.Bottom && rect.Bottom > 0 && rect.Right > 0 &&
						rect.Left < screen.WorkingArea.Right)
						WAWindows.SetWindowPos(window, WAWindows.HwndTop, rect.Left, rect.Top, rect.Width, rect.Height,
											   WAWindows.SwpShowWindow);
				}
			}
		}

		private static void ScreenWindow(Keys keys)
		{
			// Active Window
			IntPtr window = WAWindows.GetForegroundWindow();
			// Window Sreen
			Screen screen = Screen.FromHandle(window);
			// Get Screen To Move
			Screen moveToScreen = GetScreenToMove(screen, keys);
			// Screen To Move exist
			if (moveToScreen != null)
			{
				WAWindows.Rect rect;
				if (WAWindows.GetWindowRect(window, out rect))
				{
					bool isResizableWindow = IsResizableWindow(window);
					bool isFullScreenWindow = IsFullScreenWindow(rect, screen);
					MoveToNewScreen(window, screen, moveToScreen, rect, isResizableWindow, isFullScreenWindow);
				}
			}
		}

		private static bool IsFullScreenWindow(WAWindows.Rect rect, Screen scr)
		{
			return rect.Left == scr.Bounds.Left && rect.Top == scr.Bounds.Top
				   && rect.Width == scr.Bounds.Width && rect.Height == scr.Bounds.Height;
		}

		private static void MoveToNewScreen(IntPtr window, Screen screen, Screen moveToScreen, WAWindows.Rect rect, bool isResizableWindow, bool isFullScreenWindow)
		{
			Rectangle rectMoveToScreen = isFullScreenWindow ? moveToScreen.Bounds : moveToScreen.WorkingArea;
			Rectangle rectScreen = isFullScreenWindow ? screen.Bounds : screen.WorkingArea;

			double xRatio = ((double)rectMoveToScreen.Width) / ((double)rectScreen.Width);
			double yRatio = ((double)rectMoveToScreen.Height) / ((double)rectScreen.Height);

			int x = rectMoveToScreen.Left + (int)((double)(rect.Left - rectScreen.Left) * xRatio);
			int y = rectMoveToScreen.Top + (int)((double)(rect.Top - rectScreen.Top) * yRatio);
			int cx = ((isResizableWindow || isFullScreenWindow) ? (int)((double)rect.Width * xRatio) : rect.Width);
			int cy = ((isResizableWindow || isFullScreenWindow) ? (int)((double)rect.Height * yRatio) : rect.Height);

			// Fix for full screen windows
			if (isFullScreenWindow)
				WAWindows.SetWindowPos(window, WAWindows.HwndTop, x + 1, y + 1, cx - 2, cy - 2, WAWindows.SwpShowWindow);

			WAWindows.SetWindowPos(window, WAWindows.HwndTop, x, y, cx, cy, WAWindows.SwpShowWindow);
		}

		private static Screen GetScreenToMove(Screen screen, Keys keys)
		{
			foreach (Screen scr in Screen.AllScreens)
			{
				if (!scr.Equals(screen))
				{
					switch (keys)
					{
						case Keys.Left:
							if (scr.WorkingArea.Right == screen.WorkingArea.Left
								&& scr.WorkingArea.Top == scr.WorkingArea.Top)
								return scr;
							break;
						case Keys.Right:
							if (scr.WorkingArea.Left == screen.WorkingArea.Right
							   && scr.WorkingArea.Top == scr.WorkingArea.Top)
								return scr;
							break;
						case Keys.Down:
							if (scr.WorkingArea.Top == screen.WorkingArea.Bottom
							   && scr.WorkingArea.Left == scr.WorkingArea.Left)
								return scr;
							break;
						case Keys.Up:
							if (scr.WorkingArea.Bottom == screen.WorkingArea.Top
							   && scr.WorkingArea.Left == scr.WorkingArea.Left)
								return scr;
							break;
					}
				}
			}
			return null;
		}

		#endregion
	}
}
